{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Entanglement Distillation -- DEJMPS Protocol\n",
    "\n",
    "\n",
    "<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Overview\n",
    "\n",
    "Before reading this tutorial, we highly recommend you to read the [BBPSSW protocol](./EntanglementDistillation_BBPSSW_EN.ipynb) first if you are not familiar with entanglement distillation. The DEJMPS protocol, introduced by Deutsch et al. [1], is similar to the BBPSSW protocol. The main difference between these two protocols is the state for distillation: the DEJMPS protocol can distill Bell-diagonal states, while the BBPSSW protocol could distill isotropic states. In entanglement distillation, the main purpose is to generate a **maximally entangled state** $|\\Phi^+\\rangle$ from many copies of imperfect entangled states, using only LOCC operations. Recall the four Bell states,\n",
    "\n",
    "$$ \n",
    "\\begin{align*}\n",
    "|\\Phi^{\\pm}\\rangle_{AB} &= \\frac{1}{\\sqrt{2}}(|0\\rangle_A\\otimes|0\\rangle_B \\pm |1\\rangle_A\\otimes|1\\rangle_B), \\\\\n",
    "|\\Psi^{\\pm}\\rangle_{AB} &= \\frac{1}{\\sqrt{2}}(|0\\rangle_A\\otimes|1\\rangle_B \\pm |1\\rangle_A\\otimes|0\\rangle_B). \n",
    "\\tag{1}\n",
    "\\end{align*}\n",
    "$$\n",
    "\n",
    "where $A$ and $B$ represent the bi-party Alice and Bob. The Bell-diagonal state, by definition, is diagonal in the Bell basis that can be expressed as\n",
    "\n",
    "$$\n",
    "\\rho_{\\text{diag}} = p_1 | \\Phi^+\\rangle \\langle \\Phi^+ | + p_2 | \\Psi^+\\rangle \\langle \\Psi^+ | + \n",
    "p_3 | \\Phi^-\\rangle \\langle \\Phi^- |  + p_4 | \\Psi^-\\rangle \\langle \\Psi^- |,\n",
    "\\tag{2}\n",
    "$$\n",
    "\n",
    "with $p_1 > p_2 \\geq p_3 \\geq p_4$ and $p_1 + p_2+ p_3+ p_4 = 1$. Then the entanglement quantification of a Bell-diagonal state can be described as:\n",
    "\n",
    "* State fidelity $F = \\langle \\Phi^+|\\rho_{\\text{diag}}|\\Phi^+\\rangle = p_1$\n",
    "* Negativity $\\mathcal{N}(\\rho_{\\text{diag}}) = p_1 - 1/2$\n",
    "\n",
    "**Note:** The Bell-diagonal state is distillable when $p_1 > 1/2$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## DEJMPS protocol\n",
    "\n",
    "Suppose that two parties, namely Alice($A$) and Bob($B$), possess two copies of entangled qubit: $\\{ A_0, B_0 \\}, \\{ A_1, B_1 \\}$. If these two pairs are all in the same Bell-diagonal state $\\rho_{\\text{diag}}$, with $p_1 > 0.5$. We can apply the following workflow to purify the input states and leads to an output state has fidelity closer to $|\\Phi^+\\rangle$:\n",
    "1. Alice and Bob firstly choose the qubit pair **they want to keep as the memory qubit pair after distillation**. Here, they choose $A_0$ and $B_0$. \n",
    "2. Alice performs $R_x(\\pi/2)$ gates on both qubits, and Bob performs $R_x(-\\pi/2)$ gates on both qubits.\n",
    "3. Then, Alice and Bob both apply a CNOT gate on their qubits. Here, they choose $A_0,B_0$ as the control qubits and $A_1,B_1$ as the target qubits.\n",
    "4. Two remote parties measure the target qubits and use a classical communication channel to exchange their measurement results $m_{A_1}, m_{B_1}$.\n",
    "5. If the measurement results of Alice and Bob are the same (00 or 11), the distillation is successful, and the qubit pair $A_0, B_0$ is stored as state $\\rho_{out}$; If the measurement results are different (01 or 10), they claim the distillation failed and the qubit pair $A_0, B_0$ will be discarded.\n",
    "\n",
    "<center><img src=\"figures/distillation-fig-DEJMPS.jpg\" height=\"250\" width=\"300\"></center>\n",
    "<div style=\"text-align:center\">Figure 1: Schematic diagram of the DEJMPS protocol </div>\n",
    "\n",
    "After the distillation, the final state $\\rho_{out}$ of entangled pair $A_0, B_0$ will have higher fidelity than the initial state $\\rho$. The fidelity of the final state $F_{out}$ is\n",
    "\n",
    "$$\n",
    "F_{out} = \\frac{p_1^2 + p_4^2}{(p_1 + p_4)^2 + (p_2 + p_3)^2}.\n",
    "\\tag{3}\n",
    "$$\n",
    "\n",
    "Similar to the BBPSSW protocol, the DEJMPS protocol is probabilistic, with the probability of a successful distillation being \n",
    "\n",
    "$$ \n",
    "p_{succ} = (p_1 + p_4)^2 + (p_2 + p_3)^2.\n",
    "\\tag{4}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Simulation with Paddle Quantum\n",
    "First, we need to import relevant packages:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-02-23T09:13:33.570407Z",
     "start_time": "2021-02-23T09:13:30.832819Z"
    }
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from paddle_quantum.locc import LoccNet\n",
    "from paddle import matmul, trace\n",
    "import paddle\n",
    "from paddle_quantum.state import bell_state, isotropic_state, bell_diagonal_state\n",
    "from paddle_quantum.utils import negativity, logarithmic_negativity, is_ppt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let us see the theoretical result of applying the **DEJMPS protocol** to the state\n",
    "\n",
    "$$\n",
    "\\rho = p_1 | \\Phi^+\\rangle \\langle \\Phi^+ | + \\frac{1-p_1}{2} | \\Psi^+\\rangle \\langle \\Psi^+ |+ \n",
    "\\frac{1-p_1}{3}| \\Phi^-\\rangle \\langle \\Phi^- |  + \\frac{1-p_1}{6} | \\Psi^-\\rangle \\langle \\Psi^- |.\n",
    "\\tag{5}\n",
    "$$\n",
    "\n",
    "Suppose we take $p_1 = 0.7$, then the theoretical improvement of fidelity can be calculated by the following block:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-02-23T09:13:33.689788Z",
     "start_time": "2021-02-23T09:13:33.669677Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The input fidelity is: 0.7\n",
      "The output fidelity is: 0.7879999999999999\n",
      "With a probability of success: 0.625\n",
      "The input state satisfies the PPT condition and hence not distillable? False\n"
     ]
    }
   ],
   "source": [
    "def DEJMPS_metrics(*p):\n",
    "    \"\"\"\n",
    "    Returns output fidelity and probability of success of the DEJMPS protocol.\n",
    "    \"\"\"\n",
    "    F_in = p[0]\n",
    "    p_succ = (p[0] + p[3]) ** 2 + (p[1] + p[2]) ** 2\n",
    "    F_out = (p[0] ** 2 + p[3] ** 2)/p_succ\n",
    " \n",
    "    return F_in, F_out, p_succ\n",
    "\n",
    "p = 0.7\n",
    "F_in, F_out, p_succ = DEJMPS_metrics(p, (1-p)/2, (1-p)/3, (1-p)/6)\n",
    "print(\"The input fidelity is:\", F_in)\n",
    "print(\"The output fidelity is:\", F_out)\n",
    "print(\"With a probability of success:\", p_succ)\n",
    "print(\"The input state satisfies the PPT condition and hence not distillable?\", \n",
    "      is_ppt(bell_diagonal_state(p, (1-p)/2, (1-p)/3, (1-p)/6)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we can simulate the DEJMPS protocol and check if the results match with the theory."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-02-23T09:13:35.397596Z",
     "start_time": "2021-02-23T09:13:35.383842Z"
    }
   },
   "outputs": [],
   "source": [
    "class LOCC(LoccNet):\n",
    "    def __init__(self):\n",
    "        super(LOCC, self).__init__()\n",
    "        \n",
    "        # Add the first party Alice \n",
    "        # The first parameter 2 stands for how many qubits A holds\n",
    "        # The second parameter records the name of this qubit holder\n",
    "        self.add_new_party(2, party_name='Alice')\n",
    "        \n",
    "        # Add the second party Bob\n",
    "        # The first parameter 2 stands for how many qubits A holds\n",
    "        # The second parameter records the name of this qubit holder\n",
    "        self.add_new_party(2, party_name='Bob')\n",
    "        \n",
    "        # Define the input quantum states rho_in\n",
    "        _state = paddle.to_tensor(bell_diagonal_state(p, (1-p)/2, (1-p)/3, (1-p)/6))\n",
    "        \n",
    "        # ('Alice', 0) means Alice's first qubit A0\n",
    "        # ('Bob', 0) means Bob's first qubit B0\n",
    "        self.set_init_state(_state, [('Alice', 0), ('Bob', 0)]) \n",
    "        \n",
    "        # ('Alice', 1) means Alice's second qubit A1\n",
    "        # ('Bob', 1) means Bob's second qubit B1\n",
    "        self.set_init_state(_state, [('Alice', 1), ('Bob', 1)])  \n",
    "        \n",
    "        # Set the angles of the Rx gates\n",
    "        self.theta1 = paddle.to_tensor(np.array([np.pi/2, np.pi/2], dtype='float64'))\n",
    "        self.theta2 = paddle.to_tensor(np.array([-np.pi/2, -np.pi/2], dtype='float64'))\n",
    "        \n",
    "    def DEJMPS(self):\n",
    "        status = self.init_status\n",
    "        \n",
    "        # Create Alice's local operations \n",
    "        cir1 = self.create_ansatz('Alice')\n",
    "        cir1.rx(self.theta1[0], 0)\n",
    "        cir1.rx(self.theta1[1], 1)\n",
    "        cir1.cnot([0, 1])\n",
    "\n",
    "        # Create Bob's local operations \n",
    "        cir2 = self.create_ansatz('Bob')\n",
    "        cir2.rx(self.theta2[0], 0)\n",
    "        cir2.rx(self.theta2[1], 1)\n",
    "        cir2.cnot([0, 1])\n",
    "    \n",
    "        # Run circuit\n",
    "        status = cir1.run(status)\n",
    "        status_mid = cir2.run(status)\n",
    "        \n",
    "        # ('Alice', 1) means measuring Alice's second qubit A1\n",
    "        # ('Bob', 1) means measuring Bob's second qubit B1\n",
    "        # ['00','11'] specifies the success condition for distillation\n",
    "        # Means Alice and Bob both measure '00' or '11'\n",
    "        status_mid = self.measure(status_mid, [('Alice', 1), ('Bob', 1)], [\"00\", \"11\"])\n",
    "        \n",
    "        # Trace out the measured qubits A1&B1\n",
    "        # Leaving only Alice’s first qubit and Bob’s first qubit A0&B0 as the memory register\n",
    "        status_fin = self.partial_state(status_mid, [('Alice', 0), ('Bob', 0)])\n",
    "        \n",
    "        return status_fin"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-02-23T09:13:43.070659Z",
     "start_time": "2021-02-23T09:13:42.826239Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The fidelity of the input quantum state is: 0.70000\n",
      "The fidelity of the purified quantum state is: 0.78800\n",
      "The probability of successful purification is: 62.500%\n",
      "========================================================\n",
      "The output state is:\n",
      " [[ 0.45 +0.j  0.   -0.j -0.   +0.j  0.338-0.j]\n",
      " [ 0.   +0.j  0.05 +0.j  0.002-0.j  0.   -0.j]\n",
      " [ 0.   +0.j  0.002+0.j  0.05 -0.j  0.   -0.j]\n",
      " [ 0.338+0.j -0.   -0.j  0.   +0.j  0.45 +0.j]]\n",
      "The initial negativity is: 0.19999999999999993\n",
      "The final negativity is: 0.28800000000000003\n"
     ]
    }
   ],
   "source": [
    "# Run DEJMPS protocol\n",
    "status_fin = LOCC().DEJMPS()\n",
    "\n",
    "# Calculate fidelity\n",
    "target_state = paddle.to_tensor(bell_state(2))\n",
    "fidelity = 0\n",
    "for status in status_fin:\n",
    "    fidelity += paddle.real(trace(matmul(target_state, status.state)))\n",
    "fidelity /= len(status_fin)\n",
    "\n",
    "# Calculate success rate\n",
    "suc_rate = sum([status.prob for status in status_fin])\n",
    "\n",
    "# Output simulation results\n",
    "print(f\"The fidelity of the input quantum state is: {p:.5f}\")\n",
    "print(f\"The fidelity of the purified quantum state is: {fidelity.numpy()[0]:.5f}\")\n",
    "print(f\"The probability of successful purification is: {suc_rate.numpy()[0]:#.3%}\")\n",
    "\n",
    "# Print the output state\n",
    "rho_out = status_fin[0].state.numpy()\n",
    "print(\"========================================================\")\n",
    "print(f\"The output state is:\\n {np.around(rho_out,4)}\")\n",
    "print(f\"The initial negativity is: {negativity(bell_diagonal_state(p, (1-p)/2, (1-p)/3, (1-p)/6))}\")\n",
    "print(f\"The final negativity is: {negativity(rho_out)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One can observe that the simulation result is in exact accordance with the theoretical values."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conclusion\n",
    "\n",
    "The DEJMPS protocol can effectively extract one entangled pair with higher fidelity from two noisy pairs. And compared to the BBPSSW protocol [2], it can be applied to Bell-diagonal states instead of isotropic states. Note that isotropic state is a special case of Bell-diagonal state. So in this sense, the DEJMPS protocol is more general than the BBPSSW protocol. However, it also shares the same disadvantages of the BBPSSW protocol including the limited type of input states and poor scalability. \n",
    "\n",
    "Next, We suggest interested readers to check the tutorial on [how to design a new distillation protocol with LOCCNet](./EntanglementDistillation_LOCCNet_EN.ipynb)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## References\n",
    "\n",
    "[1] Deutsch, David, et al. \"Quantum privacy amplification and the security of quantum cryptography over noisy channels.\" [Physical Review Letters 77.13 (1996): 2818.](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.77.2818)\n",
    "\n",
    "[2] Bennett, Charles H., et al. \"Purification of noisy entanglement and faithful teleportation via noisy channels.\" [Physical Review Letters 76.5 (1996): 722.](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.76.722)\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.9"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": true
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
